/**
	License:		Artistic license 2.0 (see license.txt, also available online at http://www.perlfoundation.org/artistic_license_2_0)
	Authors:		OrbitalLab (http://www.orbitallab.ru/moonglide/), 2007-2009

	File:			events.d
	Description:	event stuff - engine internal data transferring system
	Date:			11.11.2007 by Digited
*/
module moonglide.core.Events;

//================================================================
/// interface and mixin-template for every object, directly or indirectly used in MoonGlide engine
/// NOTE: not for service objects, like CEvent and controller states
//================================================================
interface IMoonGlideObject
{
	protected:
		void register();
		void unRegister();
		bool isRegistered();

	public:
		char[] id();
}

template TDynamicProperties()
{
	public:
		int[ char[] ]		ints;
		float[ char[] ]		floats;
		bool[ char[] ]		bools;
		char[][ char[] ]	strings;
		Object[ char[] ]	objects;
}

template TMoonGlideObject()
{
	mixin TDynamicProperties;

	protected:
		char[]	_id;
		bool	_registered;

		void	register()		{}
		void	unRegister()	{}
		bool	isRegistered()	{ return _registered; }

	public:
		char[]	id()			{ return _id; }
}

//=============================================================
/// events enums & strings
//=============================================================
const char[][] consteventtype =
[
	"system",
	"program",
	"resource",
	"gui",
	"net",
	"script",
	"audio"
];

enum eeventtype
{
	system,
	program,
	resource,
	gui,
	net,
	script,
	audio
}

//--------------------------------------------------------

const char[][] constsystemevent =
[
	"none",
	"kbkey",
	"msmove",
	"msbutton",
	"mswheel",
	"halt"
];

enum esystemevent
{
	none,
	kbkey,
	msmove,
	msbutton,
	mswheel,
	halt
}

//--------------------------------------------------------

const char[][] constprogramevent =
[
	"none",
	"exit",			// operation system teminates the app.
	"close",			// user tries to close the window. Works usually at windowed mode
	"resize",		// window resized. Works at windowed mode
	"frame",		// frame rendered
	"command"	// a custom command from one object to another
];

enum eprogramevent
{
	none,
	exit,
	close,
	resize,
	frame,
	command
}

//--------------------------------------------------------

const char[][] constresourceevent =
[
	"none",
	"created",		// resource created: loaded first time or new
	"load",			// resource must be loaded into device memory for use
	"unload",		// resource must be unloaded from device memory
	"destroyed",	// resource is destroyed: ref == 0
	"destroyall"	// all resources of that type must be unloaded
];

const enum eresourceevent
{
	none,
	created,
	load,
	unload,
	destroyed,
	destroyall
}

//-------------------------------------------------------------------

const char[][] constguievent =
[
	"none",
	"ms_enter",
	"ms_hover",
	"ms_button",
	"ms_exit",
	"kb_focused",
	"kb_key",
	"kb_unfocused",
	"action",
	"scaling",
	"enabled_change",
	"visible_change",
	"created",
	"destroyed"
];

const enum eguievent
{
	none,
	ms_enter,
	ms_hover,
	ms_button,
	ms_exit,
	kb_focused,
	kb_key,
	kb_unfocused,
	action,
	scaling,
	enabled_change,
	visible_change,
	created,
	destroyed
}
//================================================================
/// main event class
//================================================================
class CEvent
{
	mixin TDynamicProperties;

	public:
		uint				type;
		uint				subtype;

		void clearData()
		{
			type = subtype = 0;
			floats = null;
			ints = null;
			strings = null;
			bools = null;
			objects = null; // we do not destroy them on ClearData()
		}

		static CEvent opCall( uint _type = 0, uint _subtype = 0 )
		{
			return new CEvent( _type, _subtype );
		}

		this( uint _type = 0, uint _subtype = 0 )
		{
			type = _type;
			subtype = _subtype;
		}

		~this()
		{
			clearData;
		}
}

//================================================================
/// listener of events, subscriber
//================================================================
interface IEventListener : IMoonGlideObject
{
	void onEventCallback( CEvent event, IEventGenerator sender );
	void onEventReceive( CEvent event, IEventSender sender );
}

//================================================================
/// event sender - sends event to single listener
//================================================================
interface IEventSender : IMoonGlideObject
{
	void registerListener( IEventListener listener );
	void unRegisterListener();
	void sendEvent( CEvent event );
}

//================================================================
/// generator of events - can emit event to multiple listeners
//================================================================
interface IEventGenerator : IMoonGlideObject
{
	/// registers a listener for typeToListen. Returs false if listener is already registered to that type
	bool	registerListener( IEventListener listener, eeventtype type  );

	/// registers a listener for all types of events. Returns false if listener is already registered to all types
	bool	registerListenerAll( IEventListener listener );

	/// unregisters listener by type. Rerurns false if listener was not registered to that type
	bool	unRegisterListenerType( IEventListener listener, eeventtype type );

	/// unregisters listener by all event types. Returns false if listener wasn't registered to type
	bool	unRegisterListenerAll( IEventListener listener );

	/// sends event to all listeners, registered to event's type
	void generateEvent( CEvent event );

	/// passes an event through with previos author
	void passEvent( CEvent event, IEventGenerator gen );
}

typedef void delegate( CEvent event, IEventGenerator sender ) callback_dg;
typedef void delegate( CEvent event, IEventSender sender) receive_dg;

//================================================================
/// template for mixin to event senders
//================================================================
template TEventSender()
{
	mixin TMoonGlideObject;

	protected:
		receive_dg _listener;

	public:
		void registerListener( IEventListener listener )
		{
			_listener = &listener.onEventReceive;
		}

		void unRegisterListener()
		{
			_listener = null;
		}

		void sendEvent( CEvent event )
		{
			if( _listener != null )
			{
				_listener( event, this );
			}
		}
}

//================================================================
/// template for mixin to event generators
//================================================================
template TEventGenerator()
{
	mixin TMoonGlideObject;

	protected:
		callback_dg[ char[] ][ eeventtype.max + 1 ]	_listeners;

	public:
//---------------------------------------------------------------------------------------------------
		bool registerListener( IEventListener listener, eeventtype type )
		{
			if( listener.id in _listeners[ type ] )
				return false;

			_listeners[ type ][ listener.id ] = &listener.onEventCallback;
			return true;
		}

//---------------------------------------------------------------------------------------------------

		bool registerListenerAll( IEventListener listener )
		{
			static bool everywhere;
			everywhere = true;

			foreach( inout map; _listeners )
			{
				if( !(listener.id in map) )
				{
					everywhere = false;
					map[ listener.id ] = &listener.onEventCallback;
				}
			}

			return !everywhere;
		}

//---------------------------------------------------------------------------------------------------

		bool unRegisterListenerType( IEventListener listener, eeventtype type )
		{
			if( !(listener.id in _listeners[ type ]) )
				return false;

			_listeners[ type ].remove( listener.id );
			return true;
		}

//---------------------------------------------------------------------------------------------------

		bool unRegisterListenerAll( IEventListener listener )
		{
			static bool found;
			found = false;

			foreach( inout map; _listeners )
			{
				if( listener.id in map )
				{
					map.remove( listener.id );
					found = true;
				}
			}

			return !found;
		}

//---------------------------------------------------------------------------------------------------

		void generateEvent( CEvent event )
		{
			foreach( inout listener; _listeners[ event.type ] )
			{
				listener( event, this );
			}
		}

		void passEvent( CEvent event, IEventGenerator gen )
		{
			foreach( inout listener; _listeners[ event.type ] )
			{
				listener( event, gen );
			}
		}
}

unittest
{
	assert( consteventtype.length == eeventtype.max + 1, "const and enum for event type are different!" );
	assert( constsystemevent.length == esystemevent.max + 1, "const and enum for system event are different!" );
	assert( constprogramevent.length == eprogramevent.max + 1, "const and enum for program event are different!" );
	assert( constresourceevent.length == eresourceevent.max + 1, "const and enum for resource event are different!" );
	assert( constguievent.length == eguievent.max + 1, "const and enum for gui event are different!" );
}
